iT邦幫忙

2022 iThome 鐵人賽

DAY 10
0
Software Development

你知道Go是什麼嗎?系列 第 10

Day10 - 介面(interface)- Golang

  • 分享至 

  • xImage
  •  

Go的interface比較酷,一個是傳統OOP用來抽象化行為的interface,另一個是「泛用型別」,統合在一篇內一起講好了

Interface

介面 interface 是只有方法宣告,但缺乏方法實作的型別。介面定義了一組未實作的行為,將程式抽象化,建立較低耦合的系統。

若看習慣javainterface,會比較不習慣Go不使用implements,而是直接將功能拿去寫能就直接實現介面。

定義介面

定義一個名為Animal的介面,有EatRun兩種方法。

type Animal interface {
    Eat()
    Run()
}

建立Monkey並實做出Interface內的方法

package main

import "fmt"

type Animal interface {
    Eat()
    Run()
}

type Monkey struct {
    Name string
}

func (m *Monkey) Eat() {
    fmt.Printf("%s is eating bananas.\n", m.Name)
}

func (m *Monkey) Run() {
    fmt.Printf("%s is running.\n", m.Name)
}

func main() {
    monkey := Monkey{Name:"BenLee"}
    monkey.Run()
    monkey.Eat()
}

output:

BenLee is running.
BenLee is eating bananas.

  • 建立一個Animal的介面,內涵Eat()Run()兩種動物會有的行為
  • 建立一個Cat struct,並實作了Eat()Run()兩個function,對Go來說實作了Interface的功能就跟implements的概念一樣。
  • 在實作功能時要使用pointer reciever的方式來實作,不然會導致淺複製行為,不會更改到同一struct

空interface

若宣告一介面,此介面是儲存nil

type Animal interface {
    Eat()
    Run()
}

func main() {
    var animal Animal
    fmt.Println(animal)
}

輸出:

nil

將有實做該Interfacestrcut指派給nil Interface,該Interface則會在底層儲存該struct實例

func main() {
    monkey := Monkey{Name: "BenLee"}
    var animal Animal
    animal = &monkey
    fmt.Println(animal)
}

輸出:

&{BenLee}

多型

多型的概念是相同的函式傳入不同物件,會引發不同的行為,在Go裡可以透過interface來實現。

package main

import "fmt"

//首先有個Animal的界面
type Animal interface {
    Eat()
    Run()
}

//建立兩個struct,Monkey、Hamster,並實做interface
type Monkey struct {
    Name string
}
func (m *Monkey) Eat() {
    fmt.Printf("%s is eating bananas.\n", m.Name)
}
func (m *Monkey) Run() {
    fmt.Printf("%s is running.\n", m.Name)
}

type Hamster struct {
    Name string
}
func (m *Hamster) Eat() {
    fmt.Printf("%s is eating sunflower seeds.\n", m.Name)
}
func (m *Hamster) Run() {
    fmt.Printf("%s is running.\n", m.Name)
}

//寫ShowEat函式,觀賞傳入的動物吃飯ˊˇˋ,傳入的是interface
//程式會依照此interface的型別來運作。
func ShowEat(animal Animal) {
	animal.Eat()
}

func main() {
	monkey := Monkey{Name: "BenLee"}
	hamster := Hamster{Name: "Thomas"}
	ShowEat(&monkey)
	ShowEat(&hamster)
}

輸出:

BenLee is eating bananas.
Thomas is eating sunflower seeds.

萬用型別

interface如果在後面加上兩個大括號,可以把它當作一種type使用,範例如下:

func main() {
    var a interface{}
    var b interface{}
    var c interface{}

    a = 10
    b = 12.5
    c = true

    fmt.Printf("a = %d, type = %T\n", a, a)
    fmt.Printf("b = %f, type = %T\n", b, b)
    fmt.Printf("c = %t, type = %T\n", c, c)
}

output:

a = 10, type = int
b = 12.500000, type = float64
c = true, type = bool

看起來很好用對吧?這只是表面而已,interface其實還是個interface而已,看看下面這個例子

func main() {
    var a interface{} = 10
    var b interface{} = 12
    fmt.Println(a+b)
}

輸出:

invalid operation: operator + not defined on a (variable of type interface{})

再換個例子

func main() {
    var imonkey interface{}
    imonkey = &Monkey{"BenLee"}
    fmt.Println(imonkey)         // &{BenLee}
    fmt.Println(imonkey.Name)    // Error
}

輸出:

a.Name undefined (type interface{} has no field or method Name)

因此用interface{}接受到的參數或建立的變數,還需要經過assert才能進行使用。

型別斷言 (Type Assertion)

a := i.(type)

  • a: 目標變數
  • i: interface的變數
  • type: 欲轉換成的型態

這邊直接把上面例子修正好試試看

func main() {
    var ia interface{} = 10
    var ib interface{} = 12

    a := ia.(int)
    b := ib.(int)

    fmt.Println(a + b)
}

output:

22

func main() {
    var imonkey interface{}
    imonkey = &Monkey{"BenLee"}
    monkey := imonkey.(*Monkey)
    fmt.Println(monkey.Name)
}

output:

BenLee

當型態太多時,為了避免轉錯,也可以加入檢測機制
a, ok := i.(*type)

  • a: 目標變數
  • ok: 確認型別是否正確,正確則true,錯誤則false
  • i: interface的變數
  • type: 欲轉換成的型態

給一個成功的例子

func main() {
    var imonkey interface{}
    imonkey = &Monkey{"BenLee"}
    // test true
    monkey, ok := imonkey.(*Monkey)
    if ok == true {
    	fmt.Println("Assert success.\nName:", monkey.Name)
    } else {
    	fmt.Println("Assert false")
    }
}

output:

Assert success.
Name: BenLee

與一個失敗的例子,這邊要將Monkey型態的interface轉成Hamster,因此ok = false

func main() {
    var imonkey interface{}
    imonkey = &Monkey{"BenLee"}
    // test true
    monkey, ok := imonkey.(*Hamster)
    if ok == true {
    	fmt.Println("Assert success. Name:", monkey.Name)
    } else {
    	fmt.Println("Assert false")
    }
}

output:

Assert false

當然執行時不可能預先知道每個interface的型態並去寫轉換對吧?因此要先預設好可能會傳入的變數型態去幫助assert。以上面例子為例,假設我可能傳入MonkeyHamster好了

func autoAssert(inter interface{}){
    switch inter.(type) {
        case *Monkey:
            animal := inter.(*Monkey)
            fmt.Println(animal.Name)
            animal.Eat()
        case *Hamster:
            animal := inter.(*Hamster)
            fmt.Println(animal.Name)
            animal.Eat()
    }
}
func main() {
    animals := [...]Animal{
    	&Monkey{"BenLee"},
    	&Hamster{"Thomas"},
    }
    for _,v := range animals{
    	autoAssert(v)
    }
}

output:

BenLee
BenLee is eating bananas.
Thomas
Thomas is eating sunflower seeds.

透過switch的方式看interface屬於什麼型別,並去做assert


關於最後Type Assertion的部分可能描寫得不是很好,自己還沒發現較為實用的例子,也不清楚實際開發時遇到怎樣的狀況比較需要使用,之後若有遇到再回來補充

參考資料

day15 - 介面(續)
https://ithelp.ithome.com.tw/articles/10218401

[Golang] 程式設計教學:用介面 (Interface) 實踐繼承和多型
https://opensourcedoc.com/golang-programming/interface/

Golang - 深入理解 interface 常見用法
https://blog.kennycoder.io/2020/02/03/Golang-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3interface%E5%B8%B8%E8%A6%8B%E7%94%A8%E6%B3%95/

Golang Interface
https://ithelp.ithome.com.tw/articles/10204662

Interface & OOP 就說你是鴨子! 你就是要呱呱叫
https://ithelp.ithome.com.tw/articles/10215623


上一篇
Day9 - 流程控制 - Golang
下一篇
Day11 - 套件(Package)- Golang
系列文
你知道Go是什麼嗎?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言